package cn.lg.soar.mvc.aspect.log;

import cn.lg.soar.common.constant.MDCKey;
import cn.lg.soar.common.model.LogDTO;
import cn.lg.soar.common.util.ExceptionUtil;
import cn.lg.soar.common.util.IpUtil;
import cn.lg.soar.common.util.current.ICurrentUser;
import cn.lg.soar.common.util.current.ThreadContext;
import cn.lg.soar.common.util.current.UserContext;
import cn.lg.soar.core.aspect.AopEvaluationContext;
import cn.lg.soar.core.aspect.SpelExpressionUtils;
import cn.lg.soar.core.handler.log.ILogCollector;
import cn.lg.soar.core.util.InnerSerializeUtils;
import cn.lg.soar.mvc.annotations.Log;
import cn.lg.soar.mvc.enums.LogErrorModeEnum;
import cn.lg.soar.mvc.util.ServletUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 操作日志切面(基于@Log注解)
 * @author luguoxiang
 * @date 2022/4/23
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
@ConditionalOnProperty(name = "lg.mvc.log-mode", havingValue = "annotation", matchIfMissing = true)
@Aspect
public class AnnotationLogAspect {

    protected static final Logger LOGGER = LoggerFactory.getLogger(AnnotationLogAspect.class);

    @Autowired
    private ILogCollector collector;

    @Around("@annotation(annotation)")
    public Object around(ProceedingJoinPoint point, Log annotation) throws Throwable {
        LocalDateTime startTime = LocalDateTime.now();
        Object result;
        try {
            result = point.proceed();
        } catch (RuntimeException e) {
            save(point, annotation, startTime, e);
            throw e;
        }
        save(point, annotation, startTime, result);
        return result;
    }

    /**
     * 保存日志
     * @param point
     * @param point
     * @param result
     */
    private void save(ProceedingJoinPoint point, Log annotation, LocalDateTime startTime, Object result) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Object[] args = point.getArgs();
        LogDTO log = new LogDTO();
        log.setType(annotation.type());
        log.setTitle(annotation.value());
        Object object;
        if (!"".equals(annotation.content())) {
            AopEvaluationContext context = new AopEvaluationContext(method, args, result);
            object = SpelExpressionUtils.getValue(annotation.content(), context);
            if (object != null) {
                log.setContent(object.toString());
            }
        }
        log.setStartTimeAndDuration(startTime);
        log.setTenantId(ThreadContext.getTenantId());
        // 用户相关
        ICurrentUser user = UserContext.getUserNullable();
        if (user == null) {
            log.setUserId(0L);
            log.setUserType(0);
            log.setClientType(0);
        } else {
            log.setUserId(user.getId());
            log.setUserType(user.getUserType());
            log.setClientType(user.getClientType());
        }
        // 请求相关
        HttpServletRequest request = ServletUtils.getRequest();
        log.setTraceId(MDC.get(MDCKey.TRACE_ID));
        log.setClientIp(IpUtil.getRemoteIp(request));
        log.setPath(ThreadContext.getResource());
        log.setUserAgent(request.getHeader("User-Agent"));
        // java相关
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        log.setJavaMethod(className + "#" + methodName);
        switch (annotation.argsMode()) {
            case el -> {
                AopEvaluationContext context = new AopEvaluationContext(method, args);
                object = SpelExpressionUtils.getValue(annotation.argsEl(), context);
                log.setArgs(InnerSerializeUtils.toJson(object));
            }
            case index -> {
                object = point.getArgs()[annotation.argsIndex()];
                log.setArgs(InnerSerializeUtils.toJson(object));
            }
            case args -> {
                log.setArgs(InnerSerializeUtils.toJson(point.getArgs()));
            }
            default -> {}
        }
        switch (annotation.resultMode()) {
            case el -> {
                EvaluationContext context = new StandardEvaluationContext(result);
                object = SpelExpressionUtils.getValue(annotation.resultEl(), context);
                log.setResult(InnerSerializeUtils.toJson(object));
            }
            case raw -> {
                log.setResult(InnerSerializeUtils.toJson(result));
            }
            default -> {}
        }
        // 保存
        collector.add(log);
    }

    /**
     * 保存日志
     * @param point
     * @param point
     */
    private void save(ProceedingJoinPoint point, Log annotation, LocalDateTime startTime, Throwable throwable) {
        if (annotation.errorMode() == LogErrorModeEnum.not) {
            return;
        }
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        Object[] args = point.getArgs();
        LogDTO log = new LogDTO();
        log.setType(annotation.type());
        log.setTitle(annotation.value());

        Object object;
        if (!"".equals(annotation.content())) {
            AopEvaluationContext context = new AopEvaluationContext(method, args);
            object = SpelExpressionUtils.getValue(annotation.content(), context);
            if (object != null) {
                log.setContent(object.toString());
            }
        }
        log.setStartTimeAndDuration(startTime);
        log.setTenantId(ThreadContext.getTenantId());
        // 用户相关
        ICurrentUser user = UserContext.getUserNullable();
        if (user == null) {
            log.setUserId(0L);
            log.setUserType(0);
            log.setClientType(0);
        } else {
            log.setUserId(user.getId());
            log.setUserType(user.getUserType());
            log.setClientType(user.getClientType());
        }
        // 请求相关
        HttpServletRequest request = ServletUtils.getRequest();
        log.setTraceId(MDC.get(MDCKey.TRACE_ID));
        log.setClientIp(IpUtil.getRemoteIp(request));
        log.setPath(ThreadContext.getResource());
        log.setUserAgent(request.getHeader("User-Agent"));
        // java相关
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        log.setJavaMethod(className + "#" + methodName);
        switch (annotation.argsMode()) {
            case el -> {
                AopEvaluationContext context = new AopEvaluationContext(method, args);
                object = SpelExpressionUtils.getValue(annotation.argsEl(), context);
                log.setArgs(InnerSerializeUtils.toJson(object));
            }
            case index -> {
                object = point.getArgs()[annotation.argsIndex()];
                log.setArgs(InnerSerializeUtils.toJson(object));
            }
            case args -> {
                log.setArgs(InnerSerializeUtils.toJson(point.getArgs()));
            }
            default -> {}
        }
        switch (annotation.errorMode()) {
            case stack -> {
                log.setErrorStack(ExceptionUtil.getRefinedStackTrace(throwable, 20));
            }
            case msg -> {
                log.setErrorStack(throwable.getMessage());
            }
            default -> {}
        }
        // 保存
        collector.add(log);
    }

}
